package com.qidi.mvcframework.servlet.context;

import com.qidi.mvcframework.servlet.annotation.MyAutowired;
import com.qidi.mvcframework.servlet.annotation.MyController;
import com.qidi.mvcframework.servlet.annotation.MyService;
import com.qidi.mvcframework.servlet.aop.config.MyAopConfig;
import com.qidi.mvcframework.servlet.aop.support.MyAdviceSupport;
import com.qidi.mvcframework.servlet.aop.support.MyJdkDynamicAopProxy;
import com.qidi.mvcframework.servlet.beans.MyBeanWrapper;
import com.qidi.mvcframework.servlet.beans.config.MyBeanDefinition;
import com.qidi.mvcframework.servlet.support.MyBeanDefinitionReader;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 完成 Bean 的创建和DI
 */
public class MyApplicationContext {

    private MyBeanDefinitionReader reader;

    private Map<String, MyBeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<String, MyBeanWrapper> factoryBeanInstanceCache = new HashMap<>();

    private Map<String, Object> factoryBeanObjectCache = new HashMap<>();

    public MyApplicationContext(String... configLocations) {
        try {
            // 1 加载配置文件
            reader = new MyBeanDefinitionReader(configLocations);
            // 2 解析配置文件 扫描包 ==> BeanDefinition
            List<MyBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
            // 3 享元保存 BeanDefinition
            doRegistBeanDefinition(beanDefinitions);

            doAutowrited();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doAutowrited() {
        // 懒加载 调用 getBean 才触发
        // bean 还没有真正的实例化，还是配置阶段
        for (Map.Entry<String, MyBeanDefinition> entry : this.beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            Object bean = getBean(beanName);

        }
    }

    private void doRegistBeanDefinition(List<MyBeanDefinition> beanDefinitions) throws Exception {
        for (MyBeanDefinition definition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(definition.getFactoryBeanName())) {
                throw new Exception("类型 " + definition.getFactoryBeanName() + " 已经存在！！");
            }
            // 3 种 key
            beanDefinitionMap.put(definition.getFactoryBeanName(), definition);
            // 全类名
            beanDefinitionMap.put(definition.getBeanClassName(), definition);
        }
    }

    // 真正的DI是从这个方法开始的
    public Object getBean(String beanName) {
        // 1 拿到 配置信息
        MyBeanDefinition definition = this.beanDefinitionMap.get(beanName);
        // 2 反射实例化
        Object instance = instantiateBean(beanName, definition);
        // 3 封装 BeanWrapper
        MyBeanWrapper beanWrapper = new MyBeanWrapper(instance);
        // 4 保存到 IoC 容器
        factoryBeanInstanceCache.put(beanName, beanWrapper);
        // 5 执行 DI
        populateBean(beanName, definition, beanWrapper);
        return beanWrapper.getWrapperInstance();
    }

    // DI
    private void populateBean(String beanName, MyBeanDefinition beanDefinition, MyBeanWrapper beanWrapper) {
        // 可能会涉及到循环依赖
        // 用两个缓存，循环两次
        // 1 把第一次读取结果为空的 BeanDefinition 存到第一个缓存
        // 2 等第一次循环之后，第二次循环再检查第一次的缓存，再进行赋值


        Object instance = beanWrapper.getWrapperInstance();
        Class clazz = beanWrapper.getWrapperClass();

        // 这里是判断注解的类型是否需要注解，在Spring中直接判断是否是 @Component 就行了
        if (!clazz.isAnnotationPresent(MyController.class)
                && !clazz.isAnnotationPresent(MyService.class)) {
            return;
        }

        for (Field field : clazz.getDeclaredFields()) {
            if (!field.isAnnotationPresent(MyAutowired.class)) continue;

            MyAutowired annotation = field.getAnnotation(MyAutowired.class);

            // 获取自定义的名称
            String autowiredBeanName = annotation.value().trim();
            if ("".equals(autowiredBeanName)) {
                // 没有自定义的名称，就获取类型
                // 这里一般是用接口注入的，doInstance() 方法中接口在保存到 ioc 中时使用的是 getName() 而不是类名首字母小写
                autowiredBeanName = field.getType().getName();
            }

            // 设置访问
            field.setAccessible(true);

            try {
                if (factoryBeanInstanceCache.get(autowiredBeanName) == null) continue;
                field.set(instance, factoryBeanInstanceCache.get(autowiredBeanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    // 创建真正的示例对象
    private Object instantiateBean(String beanName, MyBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {

            // 单例
            if (factoryBeanInstanceCache.containsKey(beanName)) {
                return factoryBeanInstanceCache.get(beanName).getWrapperInstance();
            }

            Class<?> clazz = Class.forName(className);
            if (clazz.isInterface()) {
                // 接口保存的是全类名
                String simpleName = toLowerFirstCase(clazz.getSimpleName());
                if (factoryBeanObjectCache.containsKey(simpleName)) {
                    factoryBeanObjectCache.put(beanName, factoryBeanObjectCache.get(simpleName));
                } else {
                    throw new Exception("错误：接口不能实例化！！");
                }
            } else {
                instance = clazz.newInstance();
                // ##################### AOP 开始 #####################
                // 1 加载 AOP 配置文件
                MyAdviceSupport config = instantionAopConfig(beanDefinition);
                config.setTargetClass(clazz);
                config.setTarget(instance);
                // 判断是否需要生成 代理对象 来覆盖 原生对象
                if (config.pointCutMatch()) {
                    instance = new MyJdkDynamicAopProxy(config).getProxy();
                }
                // ##################### AOP 结束 #####################
            }
            factoryBeanObjectCache.put(beanName, instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private MyAdviceSupport instantionAopConfig(MyBeanDefinition beanDefinition) {
        MyAopConfig config = new MyAopConfig();
        config.setPointCut((String) getConfig().get("pointCut"));
        config.setAspectClass((String) getConfig().get("aspectClass"));
        config.setAspectBefore((String) getConfig().get("aspectBefore"));
        config.setAspectAfter((String) getConfig().get("aspectAfter"));
        config.setAspectAfterThrow((String) getConfig().get("aspectAfterThrow"));
        config.setAspectAfterThrowingName((String) getConfig().get("aspectAfterThrowingName"));
        return new MyAdviceSupport(config);
    }

    public Object getBean(Class beanClass) {
        return getBean(beanClass.getName());
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return new String(chars);
    }

    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    public Set<String> getBeanDefinitionNames() {
        return beanDefinitionMap.keySet();
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }
}
